什么是Advisor

官方定义

  • 下面是官方文档对Advisor定义描述。

    Spring AI Advisor API 为拦截、修改和增强 Spring 应用中的 AI 交互提供了灵活强大的方式。通过该 API,开发者能构建更复杂、可复用且易维护的 AI 组件。

    核心优势包括:封装可复用的生成式 AI 模式、转换与大语言模型(LLM)交互的数据、实现跨模型与用例的可移植性。

  • 更多的可以自行去看官方文档,在这就先不多赘述了。

  • 那么这里直接先看看如何自定义一个Advisor,在案例后再记录是什么、有什么用。

创建一个Advisor

如何创建Advisor

  • 官方提供了两个Advisor接口,分别是CallAdvisor、StreamAdvisor接口。这两接口有什么作用?作用就是实现它们之后,使用ChatClient调用AI的过程中会执行实现这俩接口的方法。
  • 那么从命名上就能看出来它们一个是call调用、一个是stream调用相对应的接口了。
  • 所以我们定义一个自己Advisor类,实现这两个接口,并重写他们所有的方法。方法里面就日志输出一下就是了。

创建一个Log输出的Advisor

  • 实现CallAdvisor、StreamAdvisor接口,并重写所有方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @Slf4j
    public class LogAdvisor implements CallAdvisor, StreamAdvisor {

    @Override
    public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
    return null;
    }

    @Override
    public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
    return null;
    }

    @Override
    public String getName() {
    return "";
    }

    @Override
    public int getOrder() {
    return 0;
    }
    }
  • 上面可以看到,一共重写了四个方法

    • getOrder():这个是排序用的,当有多个Advisor的时候,决定执行顺序。这里我们先不管
    • getName()就定定义一个名称,这里就用类名做名称好了。
    • adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain):这个其实是CallAdvisor接口的方法,也就是使用call调用时会进入到这个方法里面。
    • adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain):这个就相对应时stream调用了。
  • 两个adviseXXX方法我们就用log输出一下提示词的内容就好了,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @Override
    public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
    log.info("\nadviseCall -> {}", chatClientRequest.prompt().getContents());
    return callAdvisorChain.nextCall(chatClientRequest);
    }

    @Override
    public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
    log.info("\nadviseStream -> {}", chatClientRequest.prompt().getContents());
    return streamAdvisorChain.nextStream(chatClientRequest);
    }

    @Override
    public String getName() {
    return this.getClass().getSimpleName();
    }

    @Override
    public int getOrder() {
    return 0;
    }
    • 在上面我们可以看到两个方法的return都是xxxAdvisorChain.nextXxx(chatClientRequest),看着有种似曾相识的感觉。
    • 到这里,自己创建的Advisor就可以了。那怎么让它在ChatClient调用AI过程中被执行呢?又没有注入到Spring容器。

添加Advisor到ChatClient

  • 在创建ChatClient的时候,添加需要的Advisor。如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Configuration
    public class LogAdvisorClientConfiguration {

    @Bean
    public ChatClient logAdvisorClient(DeepSeekChatModel chatModel) {
    return ChatClient.builder(chatModel)
    // 添加默认的 Advisors
    .defaultAdvisors(List.of(new LogAdvisor()))
    .build();
    }

    }
  • 这样就可以了,然后写个调用的案例。如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    @Slf4j
    @Component
    @RequiredArgsConstructor
    public class LogAdvisorClientExample {

    public final ChatClient logAdvisorClient;

    private static final String SYSTEM_PROMPT = "你是一个Java专家,请帮忙解答提出的Java相关问题。";

    public void logExample() {
    String content = logAdvisorClient.prompt()
    .system(SYSTEM_PROMPT)
    .user("Java 之父的全称叫什么?")
    .call()
    .content();
    log.info("\n[call] Java 之父 -> {}", content);

    Flux<String> contentFlux = logAdvisorClient.prompt()
    .system(SYSTEM_PROMPT)
    .user("Java 之父的全称叫什么?")
    .stream()
    .content();
    content = contentFlux.collectList().block().stream().collect(Collectors.joining());
    log.info("\n[stream] Java 之父 -> {}", content);
    }

    }
  • 结果。从输出上看,执行了 LogAdvisor 中的log输出。

    • adviseCall

      1
      2
      2025-06-26T19:26:48.694+08:00  INFO 77686 --- [spring-ai-example] [           main] c.s.ai.example.advisor.one.LogAdvisor    : 
      adviseCall -> 你是一个Java专家,请帮忙解答提出的Java相关问题。Java 之父的全称叫什么?
    • adviseStream

      1
      2
      2025-06-26T19:27:02.136+08:00  INFO 77686 --- [spring-ai-example] [           main] c.s.ai.example.advisor.one.LogAdvisor    : 
      adviseStream -> 你是一个Java专家,请帮忙解答提出的Java相关问题。Java 之父的全称叫什么?

重新回到什么是Advisor

  • 从上面的案例中可以看出来,在使用ChatClient调用AI的过程中,会执行添加在ChatClient中的Advisor adviseCalladviseStream方法。

  • getOrder()方法,以及在ChatClient中添加Advisor .defaultAdvisors(List.of(new LogAdvisor()))方法,可以看出来,是允许添加多个Advisor 的。

  • 多个Advisor 那么就会形成一个调用链,所以在上面案例中可以看到如下两行代码的,这个xxxAdvisorChain就是调用链。

    return callAdvisorChain.nextCall(chatClientRequest);

    return streamAdvisorChain.nextStream(chatClientRequest);

  • 那么简单来说,SpringAI会把添加在ChatClient中的Advisor 按照getOrder()来排序,形成AdvisorChain调用连。并在ChatClient调用AI的过程中执行这个AdvisorChain

  • 这就能理解官网说的Advisor 为拦截、修改和增强应用中的 AI 交互提供了灵活强大的方式。通过该 API,开发者能构建更复杂、可复用且易维护的 AI 组件。确实如此!

Advisor上下文

  • Advisor执行过程中的上下文,可以将一些参数放在里面进行共享传递。其实就是一个Map<String, Object>

  • 在创建ChatClient或者进行AI调用的时候就可以将参数放入上下文进行传递。下面将创建ChatClient的过程修改了一下。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Bean
    public ChatClient logAdvisorClient(DeepSeekChatModel chatModel) {
    return ChatClient.builder(chatModel)
    .defaultAdvisors(spec -> {
    // 参数传递
    spec.params(Map.of(
    "ClientName", "multiAdvisorClient"
    ));

    // 添加 advisors
    spec.advisors(List.of(new LogAdvisor()));
    })
    .build();
    }
  • 然后在Advisor之中通过ChatClientRequest获取或者添加参数

    1
    2
    3
    4
    // 获取
    Object clientName = chatClientRequest.context().get("ClientName");
    // 添加
    chatClientRequest.context().put("K", "V");
  • 并不复杂,这里就不放运行结果了。

最后

  • 如果对Web或者Spring比较了解的话,从Advisor的使用及功能上看,是不是觉得似曾相识?
  • 下一篇先记录一下Advisor的核心实现及执行流程。
  • 后续会继续记录几个简单的案例来尝试拦截、增强与AI的交互。
  • 所有案例的源码,都会提交在GitHub上。包:com.spring.ai.example.advisor.one